/*
 * Copyright © 2018 CODESTD.COM Inc. All rights reserved.
 */
package com.codestd.security.shiro.jwt;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.codestd.security.model.SysUser;
import com.codestd.security.redis.model.JwtTokenHash;
import com.codestd.security.redis.repository.JwtTokenHashRedisRepository;
import org.apache.shiro.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Calendar;
import java.util.Date;
import java.util.Optional;
import java.util.UUID;

/**
 * 维护JWTToken
 *
 * @author jaune
 * @since 1.0.0
 */
@Component
public class JwtTokenManagerImpl implements JwtTokenManager {

    @Autowired
    private JwtTokenHashRedisRepository redisRepository;

    private String secret = "codestd.com"; // 可以随意设置，越长越不容易破解

    @Override
    public String createToken(SysUser sysUser) {
        return this.createToken(uuid(), sysUser);
    }

    @Override
    public String createToken(String jwtId, SysUser sysUser) {
        Algorithm algorithm = Algorithm.HMAC256(this.secret);
        String token = JWT.create()
                .withIssuer("CODESTD")
                .withSubject(sysUser.getUsername())
                .withIssuedAt(new Date())
                .withKeyId(sysUser.getUserId())
                .withJWTId(jwtId)
                .sign(algorithm);
        this.redisRepository.save(new JwtTokenHash(jwtId, sysUser.getUserId(), 30L));
        return token;
    }

    @Override
    public String createTokenForMobile(SysUser sysUser) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.MINUTE, 30);

        Algorithm algorithm = Algorithm.HMAC256(this.secret);
        return JWT.create()
                .withIssuer("CODESTD")
                .withSubject(sysUser.getUsername())
                .withIssuedAt(new Date())
                .withKeyId(sysUser.getUserId())
                .withExpiresAt(calendar.getTime())
                .withClaim("type", "MOBILE")
                .sign(algorithm);
    }

    @Override
    public void refreshToken(String token) {
        try {
            DecodedJWT jwt = this.getJwt(token);
            if (this.isMobileToken(jwt)) {
                return;
            }
            this.redisRepository.save(new JwtTokenHash(jwt.getId(), jwt.getKeyId(), 30L));
        } catch (JWTVerificationException ex) {
            // ignore
        }
    }

    @Override
    public void deleteToken(String token) {
        try {
            DecodedJWT jwt = this.getJwt(token);
            if (this.isMobileToken(jwt)) {
                return;
            }
            String jwtId = jwt.getId();
            this.redisRepository.deleteById(jwtId);
        } catch (JWTVerificationException ex) {
            // ignore
        }

    }

    @Override
    public boolean checkToken(String token) {
        if (!StringUtils.hasText(token)) {
            return false;
        }
        try {
            DecodedJWT jwt = this.getJwt(token);
            if (isMobileToken(jwt)) {
                return true;
            }
            String jwtId = jwt.getId();
            Optional<JwtTokenHash> optional = this.redisRepository.findById(jwtId);
            return optional.isPresent();
        } catch (JWTVerificationException ex) {
            return false;
        }
    }

    @Override
    public SysUser resolve(String token) {
        if (this.checkToken(token)) {
            DecodedJWT jwt = this.getJwt(token);
            SysUser user = new SysUser();
            user.setUserId(jwt.getKeyId());
            user.setUsername(jwt.getSubject());
            return user;
        }
        return null;
    }

    private boolean isMobileToken(DecodedJWT jwt) {
        return jwt.getClaim("type") != null && "MOBILE".equals(jwt.getClaim("type"));
    }

    private DecodedJWT getJwt(String token) {
        Algorithm algorithm = Algorithm.HMAC256(this.secret);
        JWTVerifier verifier = JWT.require(algorithm)
                .withIssuer("CODESTD")
                .build(); //Reusable verifier instance
        return verifier.verify(token);
    }


    private String uuid() {
        UUID uuid = UUID.randomUUID();
        return uuid.toString().replace("-", "");
    }

}
